# -*- coding: utf-8 -*-
"""
Created on Fri Jun 13 16:50:41 2014

@author: Maxim
"""

from performance import SteadyLevelFlight, ClimbDescent, FlightConditions
from numpy import zeros,linspace,float64,arange
import convert

class MissionResults:
    def __init__(self):
        self.time          = 0.0
        self.distance      = 0.0
        self.altitudeStart = 0.0
        self.altitudeEnd   = 0.0
        self.fuelStart     = 0.0
        self.fuelEnd       = 0.0
        self.fuelBurned    = 0.0
#        self.aircraftMassStart = 0.0
#        self.aircraftMassEnd   = 0.0
    def __repr__(self):
        out = ''
        for attr, value in self.__dict__.iteritems():
            if type(value) is float or type(value) is float64:
                out += '{:<15} = {:<12.5f}\n'.format(attr,value)
        return out

    def display(self,name=None):
        if not name==None:
            print name + '\n'+'='*len(name)
        print self.__repr__()


class Cruise(SteadyLevelFlight):
    def run_maximum_range(self,altitude,startFuel,endFuel,nSeg=10):
        fuelMass = linspace(startFuel,endFuel,nSeg)
        df = fuelMass[0]-fuelMass[1]
        totalTime = 0.0
        totalDist = 0.0
        for i,m in enumerate(fuelMass[:-1]):
            self.bi.update_fuel_mass(m)
            results = self.run_max_SAR(altitude)
            dist = results.SAR*df
            totalDist += dist
            time = dist/results.velocity
            totalTime += time
        results = MissionResults()
        results.distance      = totalDist
        results.time          = totalTime
        results.fuelStart     = startFuel
        results.fuelEnd       = endFuel
        results.fuelBurned    = startFuel - endFuel
        results.altitudeEnd   = altitude
        results.altitudeStart = altitude
        #results.aircraftMassStart = m0
        return results
    
    def run_maximum_endurance(self,altitude,startFuel,endFuel,nSeg=10):
        fuelMass = linspace(startFuel,endFuel,nSeg)
        df = fuelMass[0]-fuelMass[1]
        totalTime = 0.0
        totalDist = 0.0
        for i,m in enumerate(fuelMass[:-1]):
            self.bi.update_fuel_mass(m)
            results = self.run_min_fuel(altitude)
            dist = results.SAR*df
            totalDist += dist
            time = dist/results.velocity
            totalTime += time
        results = MissionResults()
        results.distance      = totalDist
        results.time          = totalTime
        results.fuelStart     = startFuel
        results.fuelEnd       = endFuel
        results.fuelBurned    = startFuel - endFuel
        results.altitudeEnd   = altitude
        results.altitudeStart = altitude
        #results.aircraftMassStart = m0
        return results


    def run_maximum_endurance_fuel(self,altitude,startFuel,totalDuration,nSeg=10):
        """
        Calculates maximum endurance
        
        Parameters
        ----------
        
        altitude : float, m
            cruise altitude
        startFuel : float, kg
            mission start fuel
        totalDuration : float, hours
            endurance time
        nSeg : int
            number of segments for integration
        """
        time = linspace(0,totalDuration,nSeg)
        dt = time[1]-time[0]
        totalFuel = 0.0
        totalDist = 0.0
        mf = startFuel
        for i in range(nSeg-1):
            self.bi.update_fuel_mass(mf)
            results = self.run_min_fuel(altitude)
            dist = results.velocity*dt
            fuel = results.fuelFlow*dt
            totalFuel += fuel
            totalDist += dist
            mf += -fuel
        
        results = MissionResults()
        results.altitudeEnd = altitude
        results.altitudeStart = altitude
        results.distance = totalDist
        results.fuelStart = startFuel
        results.fuelBurned = startFuel - mf
        results.fuelEnd = mf
        results.time = totalDuration
        return results
    
    def run_maximum_speed(self,altitude,startFuel,endFuel):
        """ attack at sea level """
        pass
    
    def run_distance_at_speed(self,altitude,speed,distance,
                              startFuel=None,nSeg=11):
        results = MissionResults()
        results.altitudeEnd = altitude
        results.altitudeStart = altitude
        results.distance = distance
        results.fuelStart = startFuel

        dist = linspace(0,distance,nSeg)
        ddist = dist[1]-dist[0]
        mf = 0.0
        dt = ddist/speed
        results.time = distance/speed
        for i in range(nSeg-1):
            self.bi.update_fuel_mass(startFuel-mf)
            self.set_results(speed,altitude)
            dmf = self.results.fuelFlow*dt
            mf += dmf
        results.fuelBurned = mf
        results.fuelEnd = startFuel-mf
        return results
    
    def run_loiter(self,altitude,speed,time,startFuel=None,nSeg=11):
        results = MissionResults()
        fc = FlightConditions(speed,altitude)
        results.altitudeEnd = altitude
        results.altitudeStart = altitude
        results.fuelStart = startFuel
        results.time = time
        t = linspace(0,time,nSeg)
        dt = t[1]-t[0]
        R = 0.0
        wfuel = 0.0
        for i in arange(nSeg):
            self.set_results(fc.velocity, altitude)
            dR = self.results.velocity *dt
            R += dR
            wf = self.results.fuelFlow *dt
            wfuel += wf
            self.bi.update_fuel_mass_burned(wf)
        results.fuelBurned = wfuel
        results.fuelEnd = startFuel - wfuel
        results.distance = R
        return results
    
    def run_cruise(self,altitudeStart,altitudeEnd,speedStart,speedEnd,
                   distance,startFuel,thrustSetting=1.0,nSeg=11):
        fc1 = FlightConditions(speedStart,altitudeStart)
        fc2 = FlightConditions(speedEnd, altitudeEnd)
        results = MissionResults()
        results.altitudeStart = altitudeStart
        results.altitudeEnd = altitudeEnd
        results.distance = distance
        results.fuelStart = startFuel
        self.bi.update_fuel_mass(startFuel)
        dist = linspace(0,distance,nSeg)
        vel = linspace(fc1.velocity, fc2.velocity, nSeg, endpoint=False)
        alt = linspace(altitudeStart, altitudeEnd, nSeg, endpoint=False)
        dR = dist[1]-dist[0]
        dv = vel[1]-vel[0]
        dh = alt[1]-alt[0]
        dv2, dh2 = dv/2.0, dh/2.0
        time = 0.0
        wfuel = 0.0
        for i,v,h in zip(arange(nSeg),vel,alt):
            self.set_results(v+dv2,h+dh2)
            dt = dR/(v+dv2)
            time += dt
            wfuel += self.results.fuelFlow*dt
            self.bi.update_fuel_mass_burned(wfuel)
        results.fuelBurned = wfuel
        results.fuelEnd = startFuel - wfuel
        return results

class Climb(ClimbDescent):
    def run_climb(self,startAltitude,endAltitude,startFuel=None,nSeg=10):
        if startFuel==None:
            startFuel = self.bi.ac.mass.fuel.mass
        self.bi.update_fuel_mass(startFuel)
        altitude = linspace(startAltitude,endAltitude,nSeg)
        dh = altitude[1]-altitude[0]
        rateOfClimb = zeros(nSeg-1)
        fuelMass = zeros(nSeg-1)
        time = zeros(nSeg-1)
        distance = zeros(nSeg-1)
        totalDist = 0.0
        totalTime = 0.0
        totalFuel = 0.0
        for i,alt in enumerate(altitude[:-1]):
            data = self.run_most_economical_climb(alt)
            #data = self.run_max_climb_rate(alt)
            rateOfClimb[i] = data.climbRate
            time[i] = dh/rateOfClimb[i]
            distance[i] = data.velocity*time[i]
            fuelMass[i] = data.fuelFlow*time[i]
            totalDist += distance[i]
            totalTime += time[i]
            totalFuel += fuelMass[i]
            self.bi.update_fuel_mass_burned(fuelMass[i])
        
        results = MissionResults()
        results.altitudeEnd = endAltitude
        results.altitudeStart = startAltitude
        results.distance = totalDist
        results.time = totalTime
        results.fuelBurned = totalFuel
        return results
    
    def run_climb2(self,startAltitude, endAltitude, startVelocity, endVelocity, 
                   nSeg=11,thrustSetting=1.0,startFuel=None):
        if startFuel==None:
            startFuel = self.bi.ac.mass.fuel.mass
        self.bi.update_fuel_mass(startFuel)
        fc1 = FlightConditions(startVelocity, startAltitude)
        fc2 = FlightConditions(endVelocity, endAltitude)
        altitude = linspace(startAltitude, endAltitude, nSeg)
        velocity = linspace(fc1.velocity, fc2.velocity, nSeg)
        dh = altitude[1]-altitude[0]
        dv = velocity[1]-velocity[0]
        RC       = zeros(nSeg-1)
        fuelMass = zeros(nSeg-1)
        time     = zeros(nSeg-1)
        distance = zeros(nSeg-1)
        i = 0
        for h,v in zip(altitude[:-1], velocity[:-1]):
            h += dh/2.
            v += dv/2.
            data = self.run_climb_rate(v,h,thrustSetting)
            RC[i] = data.climbRate
            time[i] = dh/RC[i]
            distance[i] = data.velocity*time[i]
            fuelMass[i] = data.fuelFlow*time[i]
            self.bi.update_fuel_mass_burned(fuelMass[i])
            i += 1
        results = MissionResults()
        results.distance   = sum(distance)
        results.time       = sum(time)
        results.fuelStart  = startFuel
        results.fuelBurned = sum(fuelMass)
        results.fuelEnd = startFuel - results.fuelBurned
        results.altitudeEnd = endAltitude
        results.altitudeStart = startAltitude
        return results

# --- standard missions ---
def run_mission_B15(ac=None):
    """ air assault mission """
    if ac==None:
        import aircraft_FW
        ac = aircraft_FW.load('X45C')
    #ac.display()
    # -- mission inputs --
    altField = 0.0
    altCruise = 1.0e4
    altAttack = convert.ft_to_m(2000)
    distAttack = convert.nm_to_m(200)
    timeReserve = 1800.0 # 30min
    speedMaxAttack = convert.kt_to_msec(360)
    
    Wf0 = ac.mass.fuel.mass
    # -- assumptions --
    fuelReserveStart = 0.1*Wf0
    fuelAttackStart = 0.5*Wf0
    
    # -- calculations --
    slf = Cruise(ac)
    clm = Climb(ac)
    
    # reserve fuel
    reserve = slf.run_maximum_endurance_fuel(altField,fuelReserveStart,timeReserve)
    WfReserve = reserve.fuelBurned + 0.05*Wf0

    # climb 1
    climb1 = clm.run_climb(altField, altCruise,Wf0)

    # penetration-withdrawal
    penetration = slf.run_distance_at_speed(altAttack,speedMaxAttack,distAttack,fuelAttackStart)

    slf.drop_payload()
    withdrawal = slf.run_distance_at_speed(altAttack,speedMaxAttack,distAttack,penetration.fuelEnd)

    # climb 2
    #clm.drop_payload()
    climb2 = clm.run_climb(altAttack,altCruise,withdrawal.fuelEnd)

    # maximum range
    Wfcrs = Wf0 - climb1.fuelBurned - penetration.fuelBurned - withdrawal.fuelBurned
    Wfcrs += - climb2.fuelBurned
    
    cruise = slf.run_maximum_range(altCruise,Wfcrs,WfReserve)

    # operational range
    operRange = climb1.distance + cruise.distance + penetration.distance
    operRange += withdrawal.distance + climb2.distance
    operRange *= 0.5
    return operRange


def run_mission_B11(ac=None):
    """ maximum cruise range """
    if ac==None:
        import aircraft_FW
        ac = aircraft_FW.load('X45C')
    #ac.mass.payload.remove_item('drop payload')
    slf = Cruise(ac)
    clm = Climb(ac)

    # -- mission inputs --
    altField = 0.0
    altCruise = 1.0e4
    timeReserve = 1800.0 # 30min

    Wf0 = ac.mass.fuel.mass
    # -- assumptions --
    fuelReserveStart = 0.1*Wf0
    
    # climb
    
    climb1 = clm.run_climb(altField, altCruise,Wf0)
    #climb1.display('Climb 1')

    # reserve fuel
    reserve = slf.run_maximum_endurance_fuel(altField,fuelReserveStart,timeReserve)
    #reserve.display('Reserve')
    WfReserve = reserve.fuelBurned + 0.05*Wf0
    
    # cruise
    Wfcrs = Wf0 - climb1.fuelBurned
    cruise = slf.run_maximum_range(altCruise,Wfcrs,WfReserve)
    #cruise.display('Cruise')
    
    Range = climb1.distance + cruise.distance
    #print Range/1e3
    return Range


def run_maximum_endurance(ac=None):
    if ac==None:
        import aircraft_FW
        ac = aircraft_FW.load('X45C')
    #ac.mass.payload.remove_item('drop payload')
    slf = Cruise(ac)
    clm = Climb(ac)

    # -- mission inputs --
    altField = 0.0
    altCruise = 1.0e4
    timeReserve = 1800.0 # 30min

    Wf0 = ac.mass.fuel.mass
    # -- assumptions --
    fuelReserveStart = 0.1*Wf0
    
    # climb
    
    climb1 = clm.run_climb(altField, altCruise,Wf0)
    #climb1.display('Climb 1')

    # reserve fuel
    reserve = slf.run_maximum_endurance_fuel(altField,fuelReserveStart,timeReserve)
    #reserve.display('Reserve')
    WfReserve = reserve.fuelBurned + 0.05*Wf0

    # cruise
    Wfcrs = Wf0 - climb1.fuelBurned
    cruise = slf.run_maximum_endurance(altCruise,Wfcrs,WfReserve)
    #cruise.display('Cruise')
    
    Endurance = climb1.time + cruise.time
    #print Range/1e3
    return Endurance
    

def run_mission_B10(ac=None):
    """ Suppression of enemy air defenses (SEAD) """
    if ac==None:
        ac = aircraft_FW.load('X45C')
        # -- mission inputs --
    altField = 0.0
    altCruise = ac.designGoals.cruiseAltitude
    altAttack = convert.ft_to_m(20000)
    distAttack = convert.nm_to_m(50)
    timeReserve = 1800.0 # 30min
    speedMaxAttack = convert.kt_to_msec(540)
    
    Wf0 = ac.mass.fuel.mass
    # -- assumptions --
    fuelReserveStart = 0.1*Wf0
    fuelAttackStart = 0.5*Wf0
    
    # -- calculations --
    slf = Cruise(ac)
    clm = Climb(ac)
    
    # reserve fuel
    reserve = slf.run_maximum_endurance_fuel(altField,fuelReserveStart,timeReserve)
    WfReserve = reserve.fuelBurned + 0.05*Wf0

    # climb 1
    climb1 = clm.run_climb(altField, altCruise,Wf0)

    # penetration-withdrawal
    penetration = slf.run_distance_at_speed(altAttack,speedMaxAttack,distAttack,fuelAttackStart)

    slf.drop_payload()
    withdrawal = slf.run_distance_at_speed(altAttack,speedMaxAttack,distAttack,penetration.fuelEnd)

    # climb 2
    #clm.drop_payload()
    climb2 = clm.run_climb(altAttack,altCruise,withdrawal.fuelEnd)

    # maximum range
    Wfcrs = Wf0 - climb1.fuelBurned - penetration.fuelBurned - withdrawal.fuelBurned
    Wfcrs += - climb2.fuelBurned
    
    cruise = slf.run_maximum_range(altCruise,Wfcrs,WfReserve)

    # operational range
    operRange = climb1.distance + cruise.distance + penetration.distance
    operRange += withdrawal.distance + climb2.distance
    operRange *= 0.5
    return operRange


def climb_test():
    import aircraft_FW
    ac = aircraft_FW.load('X47B')
    clm = Climb(ac)
    results = clm.run_climb2(0,1e4,0.3,0.7)
    results.display()
    results2 = clm.run_climb(0,1e4)
    results2.display()

def run_test1():
    import aircraft_FW
    ac = aircraft_FW.load('X47B')
    print run_mission_B11(ac)/1e3

if __name__=="__main__":
    run_test1()
    
#    print run_mission_B15()/1e3
#    print run_mission_B11()/1e3
#    print run_mission_B10()/1e3